{
 "cells": [
  {
   "attachments": {},
   "cell_type": "markdown",
   "metadata": {
    "dotnet_interactive": {
     "language": "csharp"
    },
    "polyglot_notebook": {
     "kernelName": "csharp"
    }
   },
   "source": [
    "[this doc on github](https://github.com/dotnet/interactive/tree/main/samples/notebooks/polyglot)\n",
    "\n",
    "# Visualizing the Johns Hopkins COVID-19 time series data\n",
    "\n",
    "Also, due to travel restrictions, you should run this at home on isolated compute.\n",
    "\n",
    "*And don't forget to wash your hands.*\n",
    "\n",
    "Since Johns Hopkins has put COVID-19 time series data on [GitHub](https://github.com/CSSEGISandData/COVID-19), let's take a look at it. We can download it using PowerShell:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "dotnet_interactive": {
     "language": "pwsh"
    }
   },
   "outputs": [],
   "source": [
    "Invoke-WebRequest -Uri \"https://raw.githubusercontent.com/CSSEGISandData/COVID-19/master/csse_covid_19_data/csse_covid_19_time_series/time_series_covid19_confirmed_global.csv\" -OutFile \"./Confirmed.csv\"\n",
    "Invoke-WebRequest -Uri \"https://raw.githubusercontent.com/CSSEGISandData/COVID-19/master/csse_covid_19_data/csse_covid_19_time_series/time_series_covid19_deaths_global.csv\" -OutFile \"./Deaths.csv\"\n",
    "Invoke-WebRequest -Uri \"https://raw.githubusercontent.com/CSSEGISandData/COVID-19/master/csse_covid_19_data/csse_covid_19_time_series/time_series_covid19_recovered_global.csv\" -OutFile \"./Recovered.csv\""
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "dotnet_interactive": {
     "language": "csharp"
    },
    "polyglot_notebook": {
     "kernelName": "csharp"
    }
   },
   "source": [
    "It needs a little cleaning up:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "dotnet_interactive": {
     "language": "csharp"
    }
   },
   "outputs": [],
   "source": [
    "using System.IO;\n",
    "using System.Text.RegularExpressions;\n",
    "\n",
    "Clean(\"Confirmed.csv\");\n",
    "Clean(\"Deaths.csv\");\n",
    "Clean(\"Recovered.csv\");\n",
    "\n",
    "void Clean(string filePath)\n",
    "{\n",
    "    var raw = File.ReadAllText(filePath);\n",
    "    var regex = new Regex(\"\\\\\\\"(.*?)\\\\\\\"\");\n",
    "    var cleaned = regex.Replace(raw, m => m.Value.Replace(\",\", \" in \"));  \n",
    "    File.WriteAllText(filePath, cleaned);\n",
    "}\n",
    "\n",
    "\"All cleaned up!\""
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "dotnet_interactive": {
     "language": "csharp"
    },
    "polyglot_notebook": {
     "kernelName": "csharp"
    }
   },
   "source": [
    "Next, let's load it into a data frame."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "dotnet_interactive": {
     "language": "csharp"
    }
   },
   "outputs": [],
   "source": [
    "#r \"nuget:Microsoft.Data.Analysis,0.2.0\""
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "dotnet_interactive": {
     "language": "csharp"
    }
   },
   "outputs": [],
   "source": [
    "using Microsoft.Data.Analysis;\n",
    "\n",
    "var deaths = DataFrame.LoadCsv(\"./Deaths.csv\");\n",
    "var confirmed = DataFrame.LoadCsv(\"./Confirmed.csv\");\n",
    "var recovered = DataFrame.LoadCsv(\"./Recovered.csv\");\n",
    "var displayedValue = display(\"Processing data\");\n",
    "var offset = 4;\n",
    "var series = new List<object>();\n",
    "for(var i = offset; i <  deaths.Columns.Count; i++){\n",
    "    var date = deaths.Columns[i].Name;\n",
    "    var deathFiltered = deaths[deaths.Columns[i].ElementwiseNotEquals(0)];\n",
    "    var confirmedFiltered = confirmed[confirmed.Columns[i].ElementwiseNotEquals(0)];\n",
    "    var recoveredFiltered = recovered[recovered.Columns[i].ElementwiseNotEquals(0)];\n",
    "\n",
    "    displayedValue.Update($\"processing {date}\");\n",
    "    series.Add(new {\n",
    "        date = date,\n",
    "        deathsSeries = new {\n",
    "            latitude = deathFiltered[\"Lat\"],\n",
    "            longitude = deathFiltered[\"Long\"],\n",
    "            data = deathFiltered.Columns[i]\n",
    "        },\n",
    "        confirmedSeries = new {\n",
    "            latitude = confirmedFiltered[\"Lat\"],\n",
    "            longitude = confirmedFiltered[\"Long\"],\n",
    "            data = confirmedFiltered.Columns[i]\n",
    "        },\n",
    "        recoveredSeries = new {\n",
    "            latitude = recoveredFiltered[\"Lat\"],\n",
    "            longitude = recoveredFiltered[\"Long\"],\n",
    "            data = recoveredFiltered.Columns[i]\n",
    "        }\n",
    "    });\n",
    "}\n",
    "\n",
    "displayedValue.Update(\"Ready.\");"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "dotnet_interactive": {
     "language": "csharp"
    },
    "polyglot_notebook": {
     "kernelName": "csharp"
    }
   },
   "source": [
    "Because we've stored our data in top-level variables (`deathsSeries`, `confirmedSeries`, `recoveredSeries`, etc.) in the C# kernel, they're accessible from JavaScript latter with [variable sharing](https://github.com/dotnet/interactive/blob/main/docs/variable-sharing.md). The data is obtained as JSON and we can plot it using the library of our choice, pulled in using [RequireJS](https://requirejs.org/). \n",
    "\n",
    "We'll use [Plotly](https://plot.ly/)."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "dotnet_interactive": {
     "language": "javascript"
    }
   },
   "outputs": [],
   "source": [
    "plot = function (plotTarget,series) {\n",
    "    let loadPlotly = interactive.configureRequire({\n",
    "        context: \"COVID\",\n",
    "        paths: {\n",
    "            plotly: \"https://cdn.plot.ly/plotly-latest.min\"\n",
    "        }\n",
    "    });\n",
    "    \n",
    "    loadPlotly([\"plotly\"], (Plotly) => {\n",
    "        if (typeof (updateInterval) !== 'undefined') {\n",
    "            clearInterval(updateInterval);\n",
    "        }\n",
    "\n",
    "        let index = 0;\n",
    "\n",
    "        if (typeof (document.getElementById(plotTarget)) !== 'undefined') {\n",
    "            var { deathsSeries, confirmedSeries, recoveredSeries,  date } = series[index];\n",
    "            var recovered = {\n",
    "                name: \"Recovered\",\n",
    "                type: \"scattergeo\",\n",
    "                mode: \"markers\",\n",
    "                geo: \"geo\",\n",
    "                lat: recoveredSeries.latitude,\n",
    "                lon: recoveredSeries.longitude,\n",
    "                text: recoveredSeries.data,\n",
    "                marker: {\n",
    "                    symbol: \"square\",\n",
    "                    color: \"Green\"\n",
    "                }\n",
    "            };\n",
    "\n",
    "            var deaths = {\n",
    "                name: \"Fatal\",\n",
    "                type: \"scattergeo\",\n",
    "                geo: \"geo2\",\n",
    "                mode: \"markers\",\n",
    "                lat: deathsSeries.latitude,\n",
    "                lon: deathsSeries.longitude,\n",
    "                text: deathsSeries.data,\n",
    "                marker: {\n",
    "                    symbol: \"circle\",\n",
    "                    color: \"Black\"\n",
    "                }\n",
    "            };\n",
    "\n",
    "            var confirmed = {\n",
    "                name: \"Total confirmed\",\n",
    "                type: \"scattergeo\",\n",
    "                geo: \"geo3\",\n",
    "                mode: \"markers\",\n",
    "                lat: confirmedSeries.latitude,\n",
    "                lon: confirmedSeries.longitude,\n",
    "                text: confirmedSeries.data,\n",
    "                marker: {\n",
    "                    symbol: \"diamond\",\n",
    "                    color: \"#DC7633\"\n",
    "                }\n",
    "            };\n",
    "            \n",
    "\n",
    "            var traces = [recovered, deaths, confirmed];\n",
    "\n",
    "            var layout = {\n",
    "                title: \"COVID-19 cases (\" + date + \")\",\n",
    "                grid: { columns: 3, rows: 1 },\n",
    "                geo: {\n",
    "                    scope: \"world\",\n",
    "                    showland: true,\n",
    "                    showcountries: true,\n",
    "                    bgcolor: \"rgb(90,90,90)\",\n",
    "                    landcolor: \"rgb(250,250,250)\",\n",
    "                    domain: {\n",
    "                        row: 0,\n",
    "                        column: 0\n",
    "                    }\n",
    "                },\n",
    "                geo2: {\n",
    "                    scope: \"world\",\n",
    "                    showland: true,\n",
    "                    showcountries: true,\n",
    "                    bgcolor: \"rgb(90,90,90)\",\n",
    "                    landcolor: \"rgb(250,250,250)\",\n",
    "                    domain: {\n",
    "                        row: 0,\n",
    "                        column: 1\n",
    "                    }\n",
    "                },\n",
    "                geo3: {\n",
    "                    scope: \"world\",\n",
    "                    showland: true,\n",
    "                    showcountries: true,\n",
    "                    bgcolor: \"rgb(90,90,90)\",\n",
    "                    landcolor: \"rgb(250,250,250)\",\n",
    "                    domain: {\n",
    "                        row: 0,\n",
    "                        column: 2\n",
    "                    }\n",
    "                }\n",
    "            };\n",
    "            if (typeof (document.getElementById(plotTarget)) !== 'undefined') {\n",
    "                Plotly.newPlot(plotTarget, traces, layout);\n",
    "            }\n",
    "            let updateCovidPlot = () => {\n",
    "                if (typeof (document.getElementById(plotTarget)) !== 'undefined') {\n",
    "                    index++;\n",
    "                    if (index === series.length) {\n",
    "                        clearInterval(updateInterval);\n",
    "                        return;\n",
    "                    }\n",
    "                    var { deathsSeries, confirmedSeries, recoveredSeries, currentSeries, date } = series[index];\n",
    "                    Plotly.animate(\"plotlyChartCovid\", {\n",
    "                        data: [\n",
    "                            {\n",
    "                                lat: recoveredSeries.latitude,\n",
    "                                lon: recoveredSeries.longitude,\n",
    "                                text: recoveredSeries.data\n",
    "                            },\n",
    "                            {\n",
    "                                lat: deathsSeries.latitude,\n",
    "                                lon: deathsSeries.longitude,\n",
    "                                text: deathsSeries.data\n",
    "                            },\n",
    "                            {\n",
    "                                lat: confirmedSeries.latitude,\n",
    "                                lon: confirmedSeries.longitude,\n",
    "                                text: confirmedSeries.data\n",
    "                            }],\n",
    "                        layout: {\n",
    "                            title: \"COVID-19 \" + date\n",
    "                        }\n",
    "                    });\n",
    "                }\n",
    "            }\n",
    "            updateInterval = setInterval(() => updateCovidPlot(), 250);\n",
    "        }\n",
    "    });\n",
    "};"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "dotnet_interactive": {
     "language": "csharp"
    },
    "polyglot_notebook": {
     "kernelName": "csharp"
    }
   },
   "source": [
    "Notice the `setInterval` call near the end of the previous cell. This rechecks the data in the kernel and updates the plot.\n",
    "\n",
    "Back on the kernel, we can now update the data so that the kernel can see it.\n",
    "\n",
    "Yes, this is a contrived example, and we're planning to support true streaming data, but it's a start."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "dotnet_interactive": {
     "language": "html"
    }
   },
   "outputs": [],
   "source": [
    "<div id=\"plotlyChartCovid\"></div>\n",
    "\n",
    "#!js\n",
    "#!share --from csharp series\n",
    "plot(\"plotlyChartCovid\",series);"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": ".NET (C#)",
   "language": "C#",
   "name": ".net-csharp"
  },
  "language_info": {
   "name": "polyglot-notebook"
  },
  "polyglot_notebook": {
   "kernelInfo": {
    "defaultKernelName": "csharp",
    "items": [
     {
      "aliases": [
       "C#",
       "c#"
      ],
      "languageName": "C#",
      "name": "csharp"
     },
     {
      "aliases": [
       "F#",
       "f#"
      ],
      "languageName": "F#",
      "name": "fsharp"
     },
     {
      "aliases": [],
      "languageName": "HTML",
      "name": "html"
     },
     {
      "aliases": [],
      "languageName": "KQL",
      "name": "kql"
     },
     {
      "aliases": [],
      "languageName": "Mermaid",
      "name": "mermaid"
     },
     {
      "aliases": [
       "powershell"
      ],
      "languageName": "PowerShell",
      "name": "pwsh"
     },
     {
      "aliases": [],
      "languageName": "SQL",
      "name": "sql"
     },
     {
      "aliases": [],
      "name": "value"
     }
    ]
   }
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
